﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;
using System.IO;

namespace Najak
{
	public partial class Scene : IDisposable
	{
		static public Scene Instance;
		static public Dictionary<string, object> DynamicContentRegistry = new Dictionary<string, object>();

		//public List<Info> dc;
		public bool HasDynamicContent
		{
			get { return DynamicContentRegistry.ContainsKey(Name); }
		}
		public Point Size;
		public Point TileDimension;
		public Vector2 VportOffset = new Vector2(0, 0);

		public Tile[,] _tiles;
		private Texture2D[] _layers;
		private const int EntityLayer = 2;

		public List<Projectile> Projectiles = new List<Projectile>();

		public Editor.EditorAction.ActionManager EditorActions;

		public bool DoNotSaveOnLeaving;
		public PlayerEntity Player;
		public List<Entity> Entities = new List<Entity>();

		private Vector2 _startLocation;

		public Random Random = new Random(354668); // Arbitrary, but constant seed
		public int Score;
		public string Name;
		public Portal.PortalTarget StartingPortal;
		public Portal.PortalTarget SendingPortal;
		public Portal.PortalTarget ReachedPortal;
		public ContentManager Content { get { return App.Instance.Content; } }
		public int Width { get { return _tiles.GetLength(0); } }
		public int Height { get { return _tiles.GetLength(1); } }

		public Scene(IServiceProvider serviceProvider, string name, Portal.PortalTarget portal)
		{
			string path = Manager.SceneFolder + name + ".txt";
			Instance = this;
			Name = name;
			SendingPortal = portal;

			EditorActions = Editor.EditorAction.ActionManager.GetOrCreate(this.Name);
			//Content = new ContentManager(serviceProvider, "Content");
			Load();

		}

		public void SaveIfNeeded()
		{
			if (Scene.Instance.EditorActions.IsDirty == true)
			{
				bool wantToSave = Editor.Yes_No.GetAnswer("Do you want to save?");

				if (wantToSave != true)
				{
					return;
				}

				bool wasSaved = SaveSceneState();
				if (wasSaved)
				{
					App.Instance.SetDebugMessage("Saved", 5);
				}
				else
				{
					App.Instance.SetDebugMessage("DID NOT SAVE", 5);
				}
			}
		}

		protected void OnLeaving()
		{
		}

		public bool SaveSceneState()
		{
			if (this != Instance)
			{
				System.Windows.Forms.MessageBox.Show("Fuck you ghost");
				return false;
			}

			State s = new State(this);
			s.Save();

			Scene.Instance.EditorActions.IsDirty = false;
			Editor.EditorAction.ActionManager.Registry.Remove(Name);
			Editor.EditorAction.ActionManager.Registry.Add(Name, EditorActions.Clone());

			App.Instance.SetDebugMessage("GameStateSaved", 3);
			return true;
		}
		public void Dispose()
		{
			Entity[] entities = Entities.ToArray();
			foreach (Entity e in entities)
			{
				e.RemoveFromScene();
			}

			Entities.Clear();
			Content.Unload();
		}

		public void Update(GameTime gameTime)
		{
			// Update all Entities
			for (int i = 0; i < Entities.Count; ++i)
			{
				Entity e = Entities[i];
				e.Update(gameTime);
			}
			for (int i = Projectiles.Count - 1; i >= 0; i--)
			{
				Projectile p = Projectiles[i];
				p.OnUpdate(App.Instance.TimeDelta);
			}
		}


		public void UpdateVportPos()
		{
			var ss = App.Instance.ScreenSize;
			VportOffset = new Vector2(-(ss.X / 2) + Player.Location.X, -(ss.Y / 2) + Player.Location.Y);
		}

		public void Draw(SpriteBatch spriteBatch)
		{
			UpdateVportPos();

			Vector2 offset = -VportOffset;

			for (int i = 0; i <= EntityLayer; ++i)
			{
				for (int j = -1; j <= 1; j++)
				{
					for (int k = -1; k <= 1; k++)
					{
						Vector2 tileoffset = offset;
						tileoffset.X += (k * 1280);
						tileoffset.Y += (j * 718);
						spriteBatch.Draw(_layers[i], tileoffset, Color.White);

					}
				}
			}

			foreach (Projectile p in Projectiles)
			{
				p.Draw(App.Instance.GameTime, spriteBatch, offset);
			}
			Texture2D borderTexture = this.Content.Load<Texture2D>("Entities/Abyss");
			int thickness = 500;
			int left = (int)-VportOffset.X - thickness;
			int top = (int)-VportOffset.Y - thickness;
			int width = 2 * thickness + (Width * Tile.Size.X);
			int height = 2 * thickness + (Height * Tile.Size.Y);

			spriteBatch.Draw(borderTexture, new Rectangle(left, top, width, thickness), Color.Black);
			spriteBatch.Draw(borderTexture, new Rectangle(left, top, thickness, height), Color.Black);

			left = (int)-VportOffset.X + Width * Tile.Size.X;

			spriteBatch.Draw(borderTexture, new Rectangle(left, top, thickness, height), Color.Black);

			top = (int)-VportOffset.Y + Height * Tile.Size.Y;
			left = (int)-VportOffset.X - thickness;

			spriteBatch.Draw(borderTexture, new Rectangle(left, top, width, thickness), Color.Black);





			DrawTiles(spriteBatch, offset);

			foreach (Entity e in Entities)
			{
				e.Draw(spriteBatch, offset);
			}


			for (int i = EntityLayer + 1; i < _layers.Length; ++i)
				spriteBatch.Draw(_layers[i], offset, Color.White);

			foreach (var e in _postDrawEntities)
			{
				e.PostDraw(spriteBatch, offset);
			}
			_postDrawEntities.Clear();
		}

		public void RegisterForPostDraw(Entity e)
		{
			_postDrawEntities.Add(e);
		}
		private List<Entity> _postDrawEntities = new List<Entity>();

		private void DrawTiles(SpriteBatch spriteBatch, Vector2 offset)
		{
			// For each tile coordinate
			for (int row = 0; row < Height; ++row)
			{
				for (int column = 0; column < Width; ++column)
				{
					Tile t = _tiles[column, row];
					t.Draw(spriteBatch, offset, column, row);
				}
			}
		}

		public Vector2 GetLocation(int x, int y)
		{
			int xp = (int)(x * Tile.Width) + (Tile.Width >> 1);
			int yp = (int)(y * Tile.Height) + (Tile.Height >> 1);
			Vector2 pos = new Vector2(xp, yp);
			return pos;
		}

		public Tile GetTile(int column, int row)
		{
			if (column < 0 || column >= Width || row < 0 || row >= Height)
				return null;

			Tile tile = _tiles[column, row];
			return tile;
		}

		public TileCollision GetCollision(int column, int row)
		{
			if (column < 0 || column >= Width || row < 0 || row >= Height)
				return TileCollision.Blocked;
			else
				return _tiles[column, row].Collision;
		}

		public Rectangle GetBounds(int x, int y)
		{
			return new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height);
		}
		private void Load()
		{
			_layers = new Texture2D[3];
			for (int i = 0; i < _layers.Length; ++i)
			{
				// Choose a random segment if each background layer for scene variety.
				int segmentIndex = Random.Next(3);
				_layers[i] = Content.Load<Texture2D>("Backgrounds/LayerBasicFloor");
			}

			State s = State.LoadFromFile(Name);
			s.Apply(this, App.IsEditorMode);
		}

		private Tile LoadTile(char tileType, int x, int y, SortedList<int, Portal.PortalTarget> portaldefs)
		{
			if (char.IsDigit(tileType))
			{
				return LoadPortal(tileType, x, y, portaldefs);
			}

			return GetTileModel(tileType);
		}

		static public Func<char, Tile> GetTileModel;

		private Tile LoadPortal(char tileType, int x, int y, SortedList<int, Portal.PortalTarget> portaldefs)
		{
			int id = tileType - '0';
			if (id == SendingPortal.Portal)
			{
				if (StartingPortal != null)
				{
					char dir = StartingPortal.Direction;
					if (dir == 'W')
						y -= 2;
					if (dir == 'A')
						x -= 2;
					if (dir == 'S')
						y += 2;
					if (dir == 'D')
						x += 2;
				}
				else
				{
					y -= 2;
				}
				Rectangle bounds = GetBounds(x, y);
				Point center = bounds.Center;
				_startLocation = new Vector2(center.X, center.Y);
				if (App.IsEditorMode)
					Player = new Editor.GameEditorPlayer(true);
				else
					Player = CreateGamePlayer(); // new GodPlayer(true);

				Player.AddToScene(this, _startLocation);
			}
			return new Portal(id, "Tiles/Portal_1", portaldefs);
		}

		static public Func<PlayerEntity> CreateGamePlayer;


		static public Func<Scene, object> CaptureDynamicContent;

		public string GetStaticContent()
		{

			List<Portal> portals = new List<Portal>();
			Scene.Instance.TrimSize();

			for (int y = 0; y < Height; y++)
			{
				for (int x = 0; x < Width; x++)
				{
					Tile t = _tiles[x, y];
					Portal p = t as Portal;
					if (p != null && p.Definition != null)
					{
						portals.Add(p);
					}
				}
			}
			if (portals.Count == 0)
			{
				System.Windows.Forms.MessageBox.Show(" Dont be that fucker...");
				return null;
			}
			System.Text.StringBuilder sb = new System.Text.StringBuilder();

			for (int y = 0; y < Height; y++)
			{
				String line = "";
				for (int x = 0; x < Width; x++)
				{
					Tile t = _tiles[x, y];
					if (t.MapCode != 0)
						line += t.MapCode;
					else
						line += ".";
				}

				sb.AppendLine(line);
			}
			sb.AppendLine("===");

			foreach (Portal p in portals)
			{
				Portal.PortalTarget d = p.Definition;
				string cp = (d.IsCheckPoint) ? ",Y" : "";
				sb.AppendLine(p.ID + ":" + d.SceneName + "," + d.Portal + "," + d.Direction + cp);
			}

			return sb.ToString();
		}

		///<summary>Sets the specified tile, and returns the Tile which was replaced by this new tile.</summary> 
		public Tile SetTile(Tile t, Coordinate loc)
		{
			if (loc.Column < 0 || loc.Row < 0 || loc.Column >= Width || loc.Row >= Height)
			{
				return null;
			}
			Tile et = _tiles[loc.Column, loc.Row];

			if (t != et && t != null)
			{
				t.OnAdding(this, loc);
				t.LoadContent(this);

				_tiles[loc.Column, loc.Row] = null;
				if (et != null)
					et.OnRemoved(this, loc.Column, loc.Row);
				_tiles[loc.Column, loc.Row] = t;

				t.OnAdded(this, loc.Column, loc.Row);
			}
			return et;
		}
		public void AddEntity(Entity e)
		{
			Entities.Add(e);
		}
		public Entity FindEntityAtScreenLocation(int x, int y)
		{
			x += (int)VportOffset.X;
			y += (int)VportOffset.Y;

			foreach (Entity e in Entities)
			{
				int radius = e.BoundingRadius;
				if (radius < 12)
					radius = 12; // Kludge

				Rectangle bounds = new Rectangle((int)e.Location.X - radius, (int)e.Location.Y - radius, radius * 2, radius * 2);
				if (bounds.Contains(x, y))
				{
					return e;
				}
			}
			return null;
		}
		public Entity FindSelectableEntityAtLocation(int x, int y)
		{
			foreach (Entity e in Entities)
			{
				if (!e.IsSelectable)
					continue; 

				int radius = e.BoundingRadius;

				Rectangle bounds = new Rectangle((int)e.Location.X - radius, (int)e.Location.Y - radius, radius * 2, radius * 2);
				if (bounds.Contains(x, y))
				{
					return e;
				}
			}
			return null;
		}

		public Coordinate GetCoordAtPixelLocation(Vector2 loc)
		{
			return GetCoordAtPixelLocation((int)loc.X, (int)loc.Y);
		}
		public Coordinate GetCoordAtPixelLocation(int x, int y)
		{
			int column = (int)Math.Floor((x + VportOffset.X) / Tile.Size.X);
			int row = (int)Math.Floor((y + VportOffset.Y) / Tile.Size.Y);
			return new Coordinate(column, row);
		}
		public Tile GetTileAtPixelLocation(int x, int y)
		{
			int column = (int)((x + VportOffset.X) / Tile.Size.X);
			int row = (int)((y + VportOffset.Y) / Tile.Size.Y);
			return GetTileAtCoordinate(new Coordinate(column, row));
		}
		public Tile GetTileAtCoordinate(Coordinate loc)
		{
			if (loc.Column < 0 || loc.Row < 0 || loc.Column >= Width || loc.Row >= Height)
			{
				return null;
			}
			else
			{
				return _tiles[loc.Column, loc.Row];
			}
		}

		public Entity GetEntityAtCoordinate(Coordinate loc)
		{
			if (loc.Column < 0 || loc.Row < 0 || loc.Column >= Width || loc.Row >= Height)
			{
				return null;
			}
			else
			{
				int x = Tile.HalfSize.X + (loc.Column * Tile.Size.X);
				int y = Tile.HalfSize.Y + (loc.Row * Tile.Size.Y);

				foreach (Entity e in Entities)
				{
					if (!e.IsSelectable)
						continue;

					int radius = e.BoundingRadius;
					if (radius < 12)
						radius = 12; // Kludge

					Rectangle bounds = new Rectangle((int)e.Location.X - radius, (int)e.Location.Y - radius, radius * 2, radius * 2);
					if (bounds.Contains(x, y))
					{
						return e;
					}
				}
				return null;
			}
		}

		private void _AdjustEntityLocations(int minColumn, int minRow)
		{
			if (minColumn == 0 && minRow == 0)
				return;

			Vector2 entityOffset = new Vector2();

			entityOffset.X = -(minColumn * Tile.Size.X);
			entityOffset.Y = -(minRow * Tile.Size.Y);

			foreach (Entity e in Entities)
			{
				Vector2 pos = e.Location;
				pos += entityOffset;
				e.Location = pos;
			}
		}

		public void TrimSize()
		{
			int minX = int.MaxValue;
			int maxX = int.MinValue;
			int minY = int.MaxValue;
			int maxY = int.MinValue;
			for (int x = 0; x < Width; x++)
			{
				for (int y = 0; y < Height; y++)
				{
					Tile t = _tiles[x, y];
					if (t != Tile.DefaultTile)
					{
						if (x < minX)
						{
							minX = x;
						}
						if (y < minY)
						{
							minY = y;
						}
						if (x > maxX)
						{
							maxX = x;
						}
						if (y > maxY)
						{
							maxY = y;
						}

					}
				}
			}
			int w = maxX - minX + 1;
			int h = maxY - minY + 1;
			if (w != Width || h != Height)
			{
				Tile[,] newTiles = new Tile[w, h];
				for (int x = 0; x < w; x++)
				{
					for (int y = 0; y < h; y++)
					{
						Tile t = _tiles[x + minX, y + minY];
						newTiles[x, y] = t;
					}
				}
				_tiles = newTiles;
				_AdjustEntityLocations(minX, minY);
				EditorActions.AdjustAppliedActionLocations(minX, minY);
			}
			for (int i = Entities.Count - 1; i >= 0; i--)
			{
				Entity e = Entities[i];
				Coordinate c = Coordinate.FromLocation(e.Location);
				int column = c.Column;
				int row = c.Row;
				if (column < 0 || row < 0 || column >= Width || row >= Height)
				{
					e.RemoveFromScene();
				}
			}
		}

		public void ExpandSize(int column, int row)
		{
			if (column < 0 || column >= Width || row < 0 || row >= Height)
			{
				int minX = Math.Min(0, column);
				int maxX = Math.Max(Width - 1, column);
				int minY = Math.Min(0, row);
				int maxY = Math.Max(Height - 1, row);

				int w = maxX - minX + 1;
				int h = maxY - minY + 1;

				Tile[,] newTiles = new Tile[w, h];

				for (int x = 0; x < w; x++)
				{
					int xs = x + minX;
					for (int y = 0; y < h; y++)
					{
						int ys = y + minY;
						if (xs < 0 || xs >= Width || ys < 0 || ys >= Height)
						{
							newTiles[x, y] = Tile.DefaultTile;
						}
						else
						{
							newTiles[x, y] = _tiles[xs, ys];
						}
					}
				}
				_tiles = newTiles;
				_AdjustEntityLocations(minX, minY);
				EditorActions.AdjustAppliedActionLocations(minX, minY);
			}
		}


	}
}
